package helpers

import (
	"embed"
	"encoding/json"
	"fmt"
	"math/rand"
	"reflect"
	"strings"

	"github.com/f5devcentral/f5-bigip-rest-go/utils"
	"gopkg.in/yaml.v3"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
)

func newRestConfig(kubeConfig string) (*rest.Config, error) {
	var config *rest.Config
	var err error
	if kubeConfig == "" {
		config, err = rest.InClusterConfig()
		if err != nil {
			return nil, err
		}
	} else {
		config, err = clientcmd.BuildConfigFromFlags("", kubeConfig)
		if nil != err {
			return nil, err
		}
	}
	return config, nil
}

func newDynamicClient(kubeConfig string) (*dynamic.DynamicClient, error) {
	config, err := newRestConfig(kubeConfig)
	if err != nil {
		return nil, err
	}
	client, err := dynamic.NewForConfig(config)
	if err != nil {
		return nil, err
	}
	return client, nil
}

func newKubeClient(kubeConfig string) (*kubernetes.Clientset, error) {
	config, err := newRestConfig(kubeConfig)
	if err != nil {
		return nil, err
	}
	client, err := kubernetes.NewForConfig(config)
	if err != nil {
		return nil, err
	}
	return client, nil
}

func Yaml2Json(data []byte) ([]byte, error) {
	var intf interface{}
	if err := yaml.Unmarshal(data, &intf); err != nil {
		return nil, err
	}

	if out, err := json.Marshal(intf); err != nil {
		return nil, err
	} else {
		return out, nil
	}
}

func Contains(fullset, subset map[string]interface{}) (bool, string) {
	for k, v2 := range subset {
		if v1, ok := fullset[k]; ok {
			if reflect.TypeOf(v1) != reflect.TypeOf(v2) {
				return false, fmt.Sprintf(
					"%s type miss match, %v expect: %s, actually: %s",
					k, v2, reflect.TypeOf(v1), reflect.TypeOf(v2))
			}
			switch vv1 := v1.(type) {
			case map[string]interface{}:
				if vv2, ok := v2.(map[string]interface{}); ok {
					if s, m := Contains(vv1, vv2); !s {
						return false, m
					}
				} else {
					return false, fmt.Sprintf("%v not type of map", vv2)
				}
			case []interface{}:
				if vv2, ok := v2.([]interface{}); ok {
					if len(vv1) != len(vv2) {
						return false, fmt.Sprintf("element length not match: %s, %s", vv1, vv2)
					}
					for i, v2Elem := range vv2 {
						if reflect.TypeOf(vv1[i]) != reflect.TypeOf(v2Elem) {
							return false, fmt.Sprintf(
								"%s %d type miss match, %v expect: %s, actually: %s",
								k, i, v2Elem, reflect.TypeOf(vv1[i]), reflect.TypeOf(v2Elem))
						}

						switch reflect.TypeOf(vv1[i]).Kind().String() {
						case "map":
							if s, m := Contains(vv1[i].(map[string]interface{}), v2Elem.(map[string]interface{})); !s {
								return false, m
							}
						default:
							if !reflect.DeepEqual(vv1[i], v2Elem) {
								return false, fmt.Sprintf("expect: %v, actually: %v", vv1[i], v2Elem)
							}
						}
					}
				} else {
					return false, fmt.Sprintf("%v not type of list", vv2)
				}
			case string:
				prefix := "should contain "
				vv2 := v2.(string)
				if strings.HasPrefix(vv2, prefix) {
					v2v := vv2[len(prefix):]
					if !strings.Contains(vv1, v2v) {
						return false, fmt.Sprintf(
							"%s value not match, %v expect: containing '%s', actually: not containing",
							k, vv1, v2v)
					}
				} else {
					if !reflect.DeepEqual(vv1, vv2) {
						return false, fmt.Sprintf("%s expect: %v, actually: %v", k, vv1, vv2)
					}
				}
			default:
				if !reflect.DeepEqual(v1, v2) {
					return false, fmt.Sprintf("expect: %v, actually: %v", v1, v2)
				}
			}
		} else if reflect.DeepEqual(v2, "should not exist") {
			// check the key should not exist
		} else {
			return false, fmt.Sprintf("missing key: %s", k)
		}
	}
	return true, ""
}

func RandIndex(length int) []int {
	m := map[int][]int{}
	for i := 0; i < length; i++ {
		k := rand.Intn(length)
		if _, f := m[k]; !f {
			m[k] = []int{}
		}
		m[k] = append(m[k], i)
	}

	rlt := []int{}
	for k := 0; k < length; k++ {
		if l, f := m[k]; !f {
			continue
		} else {
			rlt = append(rlt, l...)
		}
	}

	return rlt
}

func SplitYaml(content string) []string {
	lines := strings.Split(content, "\n")
	rlt := []string{}
	page := []string{}
	for _, line := range lines {
		if line == "---" {
			yaml := strings.Join(page, "\n")
			if yaml != "" {
				rlt = append(rlt, yaml)
			}
			page = []string{}
		} else {
			page = append(page, line)
		}
	}
	if len(page) > 0 {
		yaml := strings.Join(page, "\n")
		if yaml != "" {
			rlt = append(rlt, yaml)
		}
	}
	return rlt
}

func TenantFromUri(uri string) string {
	a := strings.Split(uri, "~")
	if len(a) <= 2 {
		return ""
	} else {
		return a[2]
	}
}

func TenantFromAS3(key, template string) ([]string, error) {
	var jd map[string]interface{}
	err := json.Unmarshal([]byte(template), &jd)
	if err != nil {
		return nil, err
	}

	rlt := []string{}
	if t, f := jd["class"]; f {
		if t == "Tenant" {
			rlt = append(rlt, key)
			return rlt, nil
		} else if !utils.Contains([]string{"ADC", "AS3"}, t.(string)) {
			return nil, nil
		}
	}
	for k, v := range jd {
		if reflect.TypeOf(v).Kind().String() == "map" {
			subb, err := json.Marshal(v)
			if err != nil {
				return nil, err
			}
			subr, err := TenantFromAS3(k, string(subb))
			if err != nil {
				return nil, err
			}
			rlt = append(rlt, subr...)
		}
	}

	return rlt, nil
}

func RecurseEmbedFiles(eb embed.FS, root string, suffix string) ([]string, error) {
	rlt := []string{}
	files, err := eb.ReadDir(root)
	if err != nil {
		return nil, err
	}
	for _, f := range files {
		sub := strings.Join([]string{root, f.Name()}, "/")
		sub = strings.TrimLeft(sub, "./")
		if f.IsDir() {
			subfs, err := RecurseEmbedFiles(eb, sub, suffix)
			if err != nil {
				return nil, err
			} else {
				rlt = append(rlt, subfs...)
			}
		} else {
			if strings.HasSuffix(sub, suffix) {
				rlt = append(rlt, sub)
			}
		}
	}
	return rlt, nil
}

func isLink(v interface{}) bool {
	if entry, ok := v.(map[string]interface{}); ok {
		if _, f := entry["reference"]; f {
			return true
		}
	}
	return false
}

func getKindFromLink(v interface{}) string {
	if !isLink(v) {
		return ""
	}
	r := v.(map[string]interface{})["reference"]
	l := r.(map[string]interface{})["link"]
	k := strings.Replace(l.(string), "https://localhost/mgmt/tm/", "", -1)
	idx := strings.LastIndex(k, "?")
	subkind := k[0:idx]
	return subkind
}

func FormatArgumentString(params []string) string {
	var args []string
	for _, p := range params {
		args = append(args, fmt.Sprintf("\"%s\"", p))
	}
	return strings.Join(args, ", ")
}
